from sanic import Request
from util.main import remote_info, jsonify
from model.auth import KeyApp, IpWhitelist
from ph_utils.crypto import hash
from ph_utils.date import timestamp
from sanic.response import json as json_res
import logging
import json

logger = logging.getLogger("auth-middleware")


def _req_parse_key(request, key):
    """解析必须的参数签名的参数

    Args:
        request (sanic.Request): 请求
        key (str): 需要获取的 key

    Returns:
        str: 解析到的值
    """
    value = request.headers.get(key)  # 1. 首先从 headers 中获取
    # 2. 从 get 请求中获取
    if value is None:
        value = request.args.get(key)
    # 3. 从请求体中获取
    if request.json and value is None:
        value = request.json.get(key)
    return value


def _parse_request_body(request):
    """解析参与签名的参数

    Args:
        request (sanic.Request): 请求

    Returns:
        dict: 请求数据字典
    """
    body = {}
    if request.method == "GET":  # 解析 GET 参数
        args = request.args
        if args is not None:
            for key in args:
                values = args.getlist(key)
                if len(values) == 1:
                    body[key] = values[0]
    else:
        bjson = request.json
        if bjson is not None:
            for key, value in bjson.items():
                # 只有基础类型的值参与签名
                if isinstance(value, (int, str, bool, float)):
                    body[key] = f"{value}"
    return body


def calc_sign(body: dict, kv: str):
    """生成签名

    Args:
        body (dict): 请求参数
        kv (str): 固定的后缀key

    Returns:
        str: 生成的签名
    """
    keys = list(body.keys())
    keys.sort()  # 按字典排序
    vs = []
    for key in keys:
        if key != "sign":
            vs.append(f"{key}={body[key]}")
    vs.append(f"key={kv}")
    return hash("&".join(vs), upper=True)


async def auth_middleware(request: Request):
    """进行权限校验的中间件

    Args:
        request (sanic.Request): 请求
    """
    rpath = request.path
    auth_api_prefix = request.app.config.get("AUTH_API_PREFIX", "").strip()
    prefix_index = rpath.find(auth_api_prefix)
    auth_ignore_path = request.app.config.get("AUTH_IGNORE_PATH", [])
    if rpath not in auth_ignore_path and prefix_index != -1:
        # 参与签名的必须为 appid,nonce_str,timestamp,sign
        start = len(auth_api_prefix) + rpath.index(auth_api_prefix)
        rpath = rpath[start:]
        # 参与签名的必须为 appid,nonce_str,timestamp,sign
        params = {
            "nonce_str": _req_parse_key(request, "nonce_str"),
            "timestamp": _req_parse_key(request, "timestamp"),
            "sign": request.token,
        }
        if not all(params.values()):
            return json_res(jsonify(code=40001))
        authApp = await KeyApp.filter(app_id=params["app_id"]).first()
        if authApp is None or authApp.status == 0:
            return json_res(jsonify(code=40001))
        # 查询 ip 白名单
        remote = remote_info(request)
        logger.info(json.dumps(remote))

        # 获取参与签名的参数
        body = _parse_request_body(request)
        body["nonce_str"] = params["nonce_str"]
        body["timestamp"] = params["timestamp"]
        csign = calc_sign(body, authApp.app_secret)
        curr_time = timestamp()
        # 验证请求时间戳跟当前时间不能超过3min
        if curr_time - int(params["timestamp"]) > 180:
            return json_res(jsonify(code=40002))
        if csign != params["sign"]:  # 验证签名
            return json_res(jsonify(code=40028))

        whitelist = await IpWhitelist.filter(app_id=params["app_id"]).values_list(
            "ip", flat=True
        )
        if remote["ip"] not in whitelist:
            return json_res(jsonify(code=40005))
        if (
            request.app.ctx.rate_limit is not None
            and request.app.ctx.rate_limit.hit(params["app_id"]) is False
        ):
            return json_res(jsonify(code=40029))
